package com.pig4cloud.pigx.contract.executer.position.supporter;

import com.pig4cloud.pigx.common.customize.contants.BigDecimalConstants;
import com.pig4cloud.pigx.common.customize.contants.SystemConfigConstants;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.MqNotAckException;
import com.pig4cloud.pigx.common.customize.utils.BigDecimalUtils;
import com.pig4cloud.pigx.contract.bo.ContractPositionCrossCalculateBO;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.executer.order.supporter.OrderCalculator;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;

/**
 * 仓位计算器
 *
 * @since 2022/8/28 19:08
 */
@Slf4j
@Component
@AllArgsConstructor
public class PositionCalculator {

    /**
     * 全仓仓位JVM中计算
     *
     * @param positionList    同一币对下的仓位信息，最多两个
     * @param symbol          币对信息
     * @param availableAmount 合约账户可用额度
     * @since 2022/9/8 14:35
     */
    public static void calculateCross(List<ContractPositionCrossCalculateBO> positionList,
                                      ContractSymbolMongo symbol,
                                      BigDecimal availableAmount,
                                      BigDecimal totalFloatProfit) {
        ContractPositionCrossCalculateBO longPosition = null;
        ContractPositionCrossCalculateBO shortPosition = null;
        boolean hasLong = false;
        boolean hasShort = false;

        // 遍历
        for (ContractPositionCrossCalculateBO position : positionList) {
            // 根据方向赋值
            if (ContractOrderEnum.Side.MORE.getValue().equals(position.getSide())) {
                longPosition = position;
                hasLong = true;
            } else {
                shortPosition = position;
                hasShort = true;
            }
        }

        // 没有需要重新计算强平价的仓位
        if (!hasLong && !hasShort) {
            return;
        }

        // 以持仓数量更多或亏损更多的仓位为基准
        ContractPosition mainPosition;
        ContractPosition otherPosition = null;

        if (hasLong && hasShort) {
            // 多 > 空
            if (longPosition.getNowNum().compareTo(shortPosition.getNowNum()) > 0) {
                mainPosition = longPosition;
                otherPosition = shortPosition;

                // 空 > 多
            } else if (shortPosition.getNowNum().compareTo(longPosition.getNowNum()) > 0) {
                mainPosition = shortPosition;
                otherPosition = longPosition;

                // 数量一样，比较亏损
            } else {
                if (longPosition.getFloatProfit().compareTo(shortPosition.getFloatProfit()) <= 0) {
                    mainPosition = longPosition;
                    otherPosition = shortPosition;

                } else {
                    mainPosition = shortPosition;
                    otherPosition = longPosition;
                }
            }

        } else if (hasLong) {
            mainPosition = longPosition;

        } else {
            mainPosition = shortPosition;
        }

        // 获得净持仓数量
        BigDecimal netNowNum = mainPosition.getNowNum().subtract(Objects.isNull(otherPosition) ? BigDecimal.ZERO : otherPosition.getNowNum());
        // 处理强平提前量
        BigDecimal liquidateAdvance = BigDecimalUtils.toPercent(symbol.getLiquidateAdvance());
        // 计算强平价
        calculateCrossLiquidate(mainPosition,
                netNowNum,
                symbol.getMarkPrice(),
                availableAmount,
                totalFloatProfit,
                liquidateAdvance);
        // 计算保证金率
        calculateCrossMarginRate(mainPosition,
                symbol.getMarkPrice(),
                availableAmount,
                totalFloatProfit);
        judgePositionCrossStatus(mainPosition);
        // 同步数据
        syncCrossPosition(mainPosition, otherPosition);
    }

    /**
     * 全仓两个仓位同步数据
     *
     * @param src    源
     * @param target 目标
     * @since 2023/2/28 11:31
     */
    private static void syncCrossPosition(ContractPosition src, ContractPosition target) {
        if (Objects.isNull(target)) {
            return;
        }

        // 两个仓位数据一致
        target.setLiquidatePrice(src.getLiquidatePrice());
        target.setNeedLiquidate(src.getNeedLiquidate());
        target.setMarginRate(src.getMarginRate());
        // 根据仓位数据设置状态
        judgePositionCrossStatus(target);
    }

    /**
     * 判断全仓状态
     *
     * @param position 仓位
     * @since 2023/2/25 20:39
     */
    public static void judgePositionCrossStatus(ContractPosition position) {
        // 需要强平
        if (CommonDictEnum.YesOrNo.YES.getValue().equals(position.getNeedLiquidate())) {
            position.setStatus(ContractPositionEnum.Status.LIQUIDATING.getValue());

            // 不需要强平
        } else {
            BigDecimal closeNum = position.getCloseNum();
            // 没有平仓数量
            if (closeNum.compareTo(BigDecimal.ZERO) == 0) {
                position.setStatus(ContractPositionEnum.Status.OPEN.getValue());
            } else {
                position.setStatus(ContractPositionEnum.Status.PARTIAL_CLOSE.getValue());
            }
        }
    }

    /**
     * 全仓计算保证金率
     *
     * @param position        仓位
     * @param markPrice       标记价
     * @param availableAmount 可用额度
     * @param floatProfit     未实现盈亏
     * @since 2023/2/28 11:02
     */
    public static void calculateCrossMarginRate(ContractPosition position,
                                                BigDecimal markPrice,
                                                BigDecimal availableAmount,
                                                BigDecimal floatProfit) {
        // 计算标准保证金
        BigDecimal standardMargin = PositionCalculator.getStandardMargin(OrderCalculator.getOrderValue(markPrice, position.getNowNum()), position.getMultiple());
        // 计算保证金率
        BigDecimal marginRate = PositionCalculator.getMarginRate(availableAmount.add(position.getMargin()), floatProfit, standardMargin);
        position.setMarginRate(marginRate);
    }

    /**
     * 全仓计算强平价
     * <p>公式：
     * <p>全仓强平价 =（标记价 -（可用额度 + 未实现盈亏 + 持仓保证金）/ 净持仓） * （1 +（强平提前量 * 方向）
     *
     * @param position         仓位
     * @param netNumNow        净持仓
     * @param markPrice        标记价
     * @param availableAmount  可用额度
     * @param floatProfit      未实现盈亏
     * @param liquidateAdvance 强平提前量
     * @since 2023/2/25 14:42
     */
    public static void calculateCrossLiquidate(ContractPosition position,
                                               BigDecimal netNumNow,
                                               BigDecimal markPrice,
                                               BigDecimal availableAmount,
                                               BigDecimal floatProfit,
                                               BigDecimal liquidateAdvance) {
        availableAmount = availableAmount.add(position.getMargin())
                .add(floatProfit);

        // 锁仓时持仓数量相等，以亏损更多的仓位为基准进行计算
        if (netNumNow.compareTo(BigDecimal.ZERO) == 0) {
            // 亏损大于持仓保证金以及可用额度，可以强平
            if (availableAmount.compareTo(BigDecimal.ZERO) <= 0) {
                position.setLiquidatePrice(markPrice);
                position.setNeedLiquidate(CommonDictEnum.YesOrNo.YES.getValue());

            } else {
                position.setLiquidatePrice(BigDecimalConstants.NEGATIVE_ONE);
                position.setNeedLiquidate(CommonDictEnum.YesOrNo.NO.getValue());
            }

            return;
        }

        BigDecimal sideNum = ContractUtils.getSideNum(position.getSide());
        BigDecimal liquidatePrice = markPrice.subtract(availableAmount
                        .divide(netNumNow, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode)
                        .multiply(sideNum))
                .multiply(BigDecimal.ONE.add(liquidateAdvance.multiply(sideNum)));

        position.setLiquidatePrice(liquidatePrice);
        position.setNeedLiquidate(checkNeedLiquidate(position.getSide(), markPrice, liquidatePrice)
                ? CommonDictEnum.YesOrNo.YES.getValue()
                : CommonDictEnum.YesOrNo.NO.getValue());
    }

    /**
     * 根据强平价、标记价判断是否需要强平
     *
     * @param side           方向（0做多、1做空）
     * @param markPrice      标记价
     * @param liquidatePrice 强平价
     * @return {@link String}
     * @since 2023/2/25 14:39
     */
    public static boolean checkNeedLiquidate(String side,
                                             BigDecimal markPrice,
                                             BigDecimal liquidatePrice) {
        // 开多，标记价 <= 强平价
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), side)) {
            return markPrice.compareTo(liquidatePrice) <= 0;

            // 开空，标记价 >= 强平价
        } else {
            return markPrice.compareTo(liquidatePrice) >= 0;
        }
    }

    /**
     * 逐仓仓位JVM中计算强平价
     * <p>持仓数量大于0时
     * <p>开多 = ((1 + 强平提前量) - 持仓保证金 / 持仓价值) * 开仓均价
     * <p>开空 = ((1 - 强平提前量) + 持仓保证金 / 持仓价值) * 开仓均价
     * <p>持仓数量等于0时，设置强平价为0，需要强平为否
     *
     * @param position 仓位信息
     * @param symbol   币对信息
     * @since 2022/9/7 14:19
     */
    public static void calculateLiquidateIsolated(ContractPosition position, ContractSymbolMongo symbol) {
        // 校验数据
        calculateLiquidateIsolatedValid(position, symbol);
        // 持仓数量等于0时，设置强平价为0，需要强平为否
        if (position.getNowNum().compareTo(BigDecimal.ZERO) == 0) {
            position.setLiquidatePrice(BigDecimal.ZERO);
            position.setNeedLiquidate(CommonDictEnum.YesOrNo.NO.getValue());
            return;
        }

        // 处理强平提前量
        BigDecimal liquidateAdvance = BigDecimalUtils.toPercent(symbol.getLiquidateAdvance());
        // 求出持仓价值
        BigDecimal positionValue = position.getNowNum().multiply(position.getOpenAvgPrice());

		/*
		持仓数量大于0时
		开多 = ((1 + 强平提前量) - 持仓保证金 / 持仓价值) * 开仓均价
		开空 = ((1 - 强平提前量) + 持仓保证金 / 持仓价值) * 开仓均价

		是否需要强平
		开多 = 标记价 <= 强平价
		开空 = 标记价 >= 强平价
		 */
        if (Objects.equals(ContractOrderEnum.Side.MORE.getValue(), position.getSide())) {
            BigDecimal liquidatePrice = BigDecimal.ONE.add(liquidateAdvance)
                    .subtract(position.getMargin().divide(positionValue, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode))
                    .multiply(position.getOpenAvgPrice());
            position.setLiquidatePrice(liquidatePrice);

            // 是否需要强平
            position.setNeedLiquidate(symbol.getMarkPrice().compareTo(liquidatePrice) <= 0
                    ? CommonDictEnum.YesOrNo.YES.getValue()
                    : CommonDictEnum.YesOrNo.NO.getValue());
        } else {
            BigDecimal liquidatePrice = BigDecimal.ONE.subtract(liquidateAdvance)
                    .add(position.getMargin().divide(positionValue, SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode))
                    .multiply(position.getOpenAvgPrice());
            position.setLiquidatePrice(liquidatePrice);

            // 是否需要强平
            position.setNeedLiquidate(symbol.getMarkPrice().compareTo(liquidatePrice) >= 0
                    ? CommonDictEnum.YesOrNo.YES.getValue()
                    : CommonDictEnum.YesOrNo.NO.getValue());
        }
    }

    /**
     * 逐仓仓位JVM中计算强平价前校验参数
     *
     * @param position 仓位信息
     * @param symbol   币对信息
     * @since 2022/9/7 14:24
     */
    public static void calculateLiquidateIsolatedValid(ContractPosition position, ContractSymbolMongo symbol) {
        if (Strings.isEmpty(position.getSide())) {
            throw new MqNotAckException("数据异常，side为空");
        }
        if (Objects.isNull(symbol.getLiquidateAdvance())) {
            throw new MqNotAckException("数据异常，liquidateAdvance为空");
        }
        if (Objects.isNull(symbol.getMarkPrice())) {
            throw new MqNotAckException("数据异常，markPrice为空");
        }
        if (Objects.isNull(position.getMargin())) {
            throw new MqNotAckException("数据异常，margin为空");
        }
        if (Objects.isNull(position.getNowNum())) {
            throw new MqNotAckException("数据异常，nowNum为空");
        }
        if (Objects.isNull(position.getOpenAvgPrice())) {
            throw new MqNotAckException("数据异常，openAvgPrice为空");
        }
    }

    /**
     * 根据仓位面值、杠杆倍数计算标准保证金
     *
     * @param positionValue 仓位面值
     * @param multiple      杠杆倍数
     * @return {@link BigDecimal}
     * @since 2022/9/7 21:40
     */
    public static BigDecimal getStandardMargin(BigDecimal positionValue, Integer multiple) {
        return positionValue.divide(BigDecimal.valueOf(multiple), SystemConfigConstants.amountScale, SystemConfigConstants.mainCurRoundingMode);
    }

    /**
     * 获得全仓仓位标准保证金 - 持仓保证金的差额
     *
     * @param openAvgPrice 开仓均价
     * @param nowNum       当前持仓数量
     * @param multiple     杠杆倍数
     * @param margin       保证金
     * @since 2022/9/7 21:57
     */
    public static BigDecimal getFullStandardMarginDifference(BigDecimal openAvgPrice, BigDecimal nowNum, Integer multiple, BigDecimal margin) {
        BigDecimal positionValue = OrderCalculator.getOrderValue(openAvgPrice, nowNum);
        // 计算出标准保证金
        BigDecimal standardMargin = PositionCalculator.getStandardMargin(positionValue, multiple);
        // 计算标准保证金和当前全仓持仓保证金的差额
        return standardMargin.subtract(margin);
    }

    /**
     * 计算保证金率
     * <p>公式：
     * <p>逐仓：（有效保证金 + 未实现盈亏） / 标准保证金
     * <p>全仓：（有效保证金 + 未实现盈亏） / 标准保证金
     *
     * @param effectiveMargin 有效保证金
     * @param profit          未实现盈亏
     * @param standardMargin  标准保证金
     * @return {@link BigDecimal}
     * @since 2022/9/17 19:36
     */
    public static BigDecimal getMarginRate(BigDecimal effectiveMargin,
                                           BigDecimal profit,
                                           BigDecimal standardMargin) {
        return effectiveMargin.add(profit)
                .divide(standardMargin, SystemConfigConstants.amountScale, SystemConfigConstants.tokenCurRoundingMode);
    }

    /**
     * 根据开仓均价、强平价、方向、当前持仓数量计算有效保证金
     * <p>公式：
     * 做多：有效保证金 = （开仓均价 - 强平价） * 1 * 当前持仓数量
     * 做空：有效保证金 = （开仓均价 - 强平价） * -1 * 当前持仓数量
     *
     * @param openAvgPrice   开仓均价
     * @param liquidatePrice 强平价
     * @param side           方向
     * @param nowNum         当前持仓数量
     * @return {@link BigDecimal}
     * @since 2022/10/12 14:37
     */
    public static BigDecimal getEffectiveMargin(BigDecimal openAvgPrice,
                                                BigDecimal liquidatePrice,
                                                String side,
                                                BigDecimal nowNum) {
        // 获取方向对应的值
        BigDecimal sideNum = ContractUtils.getSideNum(side);

        return openAvgPrice.subtract(liquidatePrice)
                .multiply(sideNum)
                .multiply(nowNum);
    }
}
